﻿using System;
using NUnit.Framework;
using System.Reflection;
using Castle.Core.Interceptor;

namespace Duhking.Tests
{
    [TestFixture]
    public class DuhkingInterceptorTests
    {
        private DisposableTestClass _targetClass;
        private DuhkingInterceptor _interceptor;

        private class DisposableTestClass
        {
            public bool HasDisposed { get; set; }

            public void Dispose()
            {
                HasDisposed = true;
            }
        }

        public interface SimpleMethodWithReturn
        {
            int DoSomething();
        }

        private class SimpleTestClass
        {
            public int DoSomething()
            {
                return 12;
            }
        }

        private interface InterfaceWithGenericMethod
        {
            T DoSomething<T, U>(U arg);
        }

        public class GenericMethodTestClass
        {
            public object Arg { get; set; }

            public T DoSomething<T, U>(U arg)
            {
                Arg = arg;
                return default(T);
            }
        }

        private class Invocation : IInvocation
        {
            public object[] Arguments { get; private set; }

            public Type[] GenericArguments { get; private set; }

            public object GetArgumentValue(int index)
            {
                throw new NotImplementedException();
            }

            public MethodInfo GetConcreteMethod()
            {
                throw new NotImplementedException();
            }

            public MethodInfo GetConcreteMethodInvocationTarget()
            {
                throw new NotImplementedException();
            }

            public object InvocationTarget { get; private set; }

            public MethodInfo Method { get; private set; }

            public MethodInfo MethodInvocationTarget { get; private set; }

            public void Proceed()
            {
                throw new NotImplementedException();
            }

            public object Proxy { get; private set; }

            public object ReturnValue { get; set; }

            public void SetArgumentValue(int index, object value)
            {
                throw new NotImplementedException();
            }

            public Type TargetType { get; private set; }

            public Invocation(MethodInfo method)
            {
                Method = method;
            }

            public Invocation(MethodInfo method, Type[] genericArguments, object[] arguments)
            {
                Method = method;
                GenericArguments = genericArguments;
                Arguments = arguments;
            }

        }

        [SetUp]
        public void Setup()
        {
            _targetClass = new DisposableTestClass();
            _interceptor = new DuhkingInterceptor(_targetClass,null);
        }

        [Test]
        public void Intercept_Throws_ObjectDisposedException_If_Target_Has_Been_Collected()
        {
            _targetClass = null;
            GC.Collect();
            Assert.Throws<ObjectDisposedException>(() => _interceptor.Intercept((IInvocation)null));
        }

        [Test]
        public void Intercept_Calls_Method_On_Target_Class_With_Signature_That_Mathces_Invocation()
        {
            var proxyMethod = typeof(IDisposable).GetMethod("Dispose");
            var invocation = new Invocation(proxyMethod);
            _interceptor.Intercept(invocation);
            Assert.That(_targetClass.HasDisposed, Is.True);
        }

        [Test]
        public void Intercept_Sets_ReturnValue_For_NonVoid_Methods()
        {
            var proxyMethod = typeof(SimpleMethodWithReturn).GetMethod("DoSomething");
            var invocation = new Invocation(proxyMethod);
            _interceptor = new DuhkingInterceptor(new SimpleTestClass(),null);
            _interceptor.Intercept(invocation);
            Assert.That(invocation.ReturnValue, Is.EqualTo(12));
        }

        [Test]
        public void Intercept_Applies_All_Generic_Type_Arguments()
        {
            var proxyMethod = typeof(InterfaceWithGenericMethod).GetMethod("DoSomething");
            proxyMethod = proxyMethod.MakeGenericMethod(new[] { typeof(bool), typeof(int) });
            var invocation = new Invocation(proxyMethod,proxyMethod.GetGenericArguments(),new[] {(object)1});
            var testClass = new GenericMethodTestClass();
            _interceptor = new DuhkingInterceptor(testClass,null);
            _interceptor.Intercept(invocation);
            Assert.That(invocation.ReturnValue, Is.Not.Null);
            Assert.That(invocation.ReturnValue, Is.False);
            Assert.That(testClass.Arg, Is.EqualTo(1));
        }
    }
}
